/**
 * 
 */
package multimedia.model.colorspace;

import java.util.Vector;

/**
 * This class is just a utility class that will convert pixels between supported colorspace as well
 * as other operations dealing with common image applications.
 * 
 * @author trigueros
 * 
 */
public class ColorspaceUtil
{
    /**
     * Compute the average of all the pixels {@link ColorspaceInterface} Vector, it should only have
     * 3 components.
     * 
     * @param pixelVector
     * @return a {@link ColorspaceInterface} object containing the average values as it's 3
     *         components.
     */
    public static ColorspaceInterface ComputeColorspaceAverage( Vector<ColorspaceInterface> pixelVector )
    {
        ColorspaceInterface averageComponents = new DefaultColorspace();

        // Add all components
        for ( ColorspaceInterface colorspace : pixelVector )
        {
            averageComponents.setFirstComponent(colorspace.getFirstComponent() + averageComponents.getFirstComponent());
            averageComponents.setSecondComponent(colorspace.getSecondComponent()
                    + averageComponents.getSecondComponent());
            averageComponents.setThirdComponent(colorspace.getThirdComponent() + averageComponents.getThirdComponent());
        }

        // Divide by the number of pixels.
        averageComponents.setFirstComponent(averageComponents.getFirstComponent() / pixelVector.size());
        averageComponents.setSecondComponent(averageComponents.getSecondComponent() / pixelVector.size());
        averageComponents.setThirdComponent(averageComponents.getThirdComponent() / pixelVector.size());

        return averageComponents;
    }

    /**
     * Used to create the initial RGB Vector from the raw pixel data read, we must change it to a
     * bigger data file so that the number we see makes sense.
     * 
     * @param rawPixelData
     * @return
     */
    public static Vector<ColorspaceInterface> CreateInitialRGBVector( byte[] rawPixelData )
    {
        Vector<ColorspaceInterface> rgbVector = new Vector<ColorspaceInterface>();

        // First convert the raw data into a bigger data type so that we can use it to do
        // computations.
        int[] rgbPixels = new int[rawPixelData.length];
        for ( int i = 0; i < rawPixelData.length; i++ )
        {
            rgbPixels[i] = (rawPixelData[i] & 0xff);
        }

        // Now let's add that data into the rgbVector
        for ( int i = 0, j = 0; i < rawPixelData.length / 3; i++, j += 3 )
        {
            rgbVector.add(new RGB(rgbPixels[j], rgbPixels[j + 1], rgbPixels[j + 2]));
        }

        return rgbVector;
    }

    /**
     * Create a byte array out of a vector so that we can constitute image.
     * 
     * @param vector
     * @param width
     * @param height
     * @return
     */
    public static byte[] ConvertToByteArray( Vector<ColorspaceInterface> vector, int width, int height )
    {
        byte[] output = new byte[width * height * 3];

        int i = 0;
        for ( ColorspaceInterface colorspace : vector )
        {
            output[i] = (byte) colorspace.getFirstComponent();
            output[i + 1] = (byte) colorspace.getSecondComponent();
            output[i + 2] = (byte) colorspace.getThirdComponent();

            i += 3;
        }

        return output;
    }

    /**
     * Take an RGB Vector and create another with the equivalent XYZ values. Source:
     * http://www.easyrgb.com/index.php?X=MATH&H=02#text2
     * 
     * @param rgbVector
     * @return
     */
    public static Vector<ColorspaceInterface> ConvertToXYZ( Vector<ColorspaceInterface> rgbVector )
    {
        Vector<ColorspaceInterface> xyzVector = new Vector<ColorspaceInterface>();

        for ( ColorspaceInterface rgb : rgbVector )
        {
            double R = rgb.getFirstComponent() / 255;
            R = (R > 0.04045) ? (Math.pow((R + 0.055) / 1.055, 2.4)) : R / 12.92;
            double G = rgb.getSecondComponent() / 255;
            G = (G > 0.04045) ? (Math.pow((G + 0.055) / 1.055, 2.4)) : G / 12.92;
            double B = rgb.getThirdComponent() / 255;
            B = (B > 0.04045) ? (Math.pow((B + 0.055) / 1.055, 2.4)) : B / 12.92;

            float x = (float) (0.4124240 * R + 0.3575790 * G + 0.1804640 * B) * 100;
            float y = (float) (0.2126560 * R + 0.7151580 * G + 0.0721856 * B) * 100;
            float z = (float) (0.0193324 * R + 0.1191930 * G + 0.9504440 * B) * 100;

            xyzVector.add(new XYZ(x, y, z));
        }

        return xyzVector;
    }

    /**
     * Given an RGB Vector it converts each pixel to the Lab Color model. Source:
     * http://www.easyrgb.com/index.php?X=MATH&H=07#text7
     * 
     * @param rgbVector
     * @return
     */
    public static Vector<ColorspaceInterface> ConvertToLAB( Vector<ColorspaceInterface> rgbVector )
    {
        Vector<ColorspaceInterface> labVector = new Vector<ColorspaceInterface>();
        Vector<ColorspaceInterface> xyzVector = ConvertToXYZ(rgbVector);

        double Xn = 95.047f;
        double Yn = 100f;
        double Zn = 108.883f;

        for ( ColorspaceInterface colorspace : xyzVector )
        {
            double X = colorspace.getFirstComponent() / Xn;
            X = (X > 0.008856) ? Math.pow(X, 1.0 / 3.0) : (7.787 * X) + (16.0 / 116.0);

            double Y = colorspace.getSecondComponent() / Yn;
            Y = (Y > 0.008856) ? Math.pow(Y, 1.0 / 3.0) : (7.787 * Y) + (16.0 / 116.0);

            double Z = colorspace.getThirdComponent() / Zn;
            Z = (Z > 0.008856) ? Math.pow(Z, 1.0 / 3.0) : (7.787 * Z) + (16.0 / 116.0);

            float L = (float) (116 * Y) - 16;
            float a = (float) (500 * (X - Y));
            float b = (float) (200 * (Y - Z));

            labVector.add(new LAB(L, a, b));
        }

        return labVector;
    }

    /**
     * Take an RGB Vector and create another with the equivalent YUV values.
     * 
     * @param rgbVector
     * @return
     */
    public static Vector<ColorspaceInterface> ConvertToYUV( Vector<ColorspaceInterface> rgbVector )
    {
        Vector<ColorspaceInterface> yuvVector = new Vector<ColorspaceInterface>();

        for ( ColorspaceInterface rgb : rgbVector )
        {
            float R = rgb.getFirstComponent();
            float G = rgb.getSecondComponent();
            float B = rgb.getThirdComponent();

            float y = (float) (0.299 * R + 0.587 * G + 0.114 * B);
            float u = (float) (-0.147 * R - 0.289 * G + 0.437 * B);
            float v = (float) (0.615 * R - 0.515 * G - 0.100 * B);

            yuvVector.add(new YUV(y, u, v));
        }

        return yuvVector;
    }

    /**
     * Take an RGB Vector and create another with the equivalent YIQ values.
     * 
     * @param rgbVector
     * @return
     */
    public static Vector<ColorspaceInterface> ConvertToYIQ( Vector<ColorspaceInterface> rgbVector )
    {
        Vector<ColorspaceInterface> yiqVector = new Vector<ColorspaceInterface>();

        for ( ColorspaceInterface rgb : rgbVector )
        {
            float R = rgb.getFirstComponent();
            float G = rgb.getSecondComponent();
            float B = rgb.getThirdComponent();

            float y = (float) (0.299 * R + 0.587 * G + 0.114 * B);
            float i = (float) (0.596 * R - 0.274 * G - 0.322 * B);
            float q = (float) (0.212 * R - 0.523 * G + 0.311 * B);

            yiqVector.add(new YIQ(y, i, q));
        }

        return yiqVector;
    }

    public static Vector<ColorspaceInterface> ConvertFromYIQ( Vector<ColorspaceInterface> yiqVector )
    {
        Vector<ColorspaceInterface> rgbVector = new Vector<ColorspaceInterface>();

        for ( ColorspaceInterface yiq : yiqVector )
        {
            float y = yiq.getFirstComponent();
            float i = yiq.getSecondComponent();
            float q = yiq.getThirdComponent();
            
            float R = (float) (1.0*y + 0.956*i + 0.621*q);
            float G = (float) (1.0*y + -0.272*i + -0.647*q);
            float B = (float) (1.0*y + -1.105*i + 1.702*q);
            
            rgbVector.add( new RGB( (int)R,(int)G,(int)B) );
        }
        
        NormalizeRGBValues(rgbVector);
        return rgbVector;
    }
    
    /**
     * Take an RGB Vector and create another with the equivalent YCbCr values.
     * 
     * @param rgbVector
     * @return
     */
    public static Vector<ColorspaceInterface> ConvertToYCbCr( Vector<ColorspaceInterface> rgbVector )
    {
        Vector<ColorspaceInterface> ycbcrVector = new Vector<ColorspaceInterface>();

        for ( ColorspaceInterface rgb : rgbVector )
        {
            float R = rgb.getFirstComponent();
            float G = rgb.getSecondComponent();
            float B = rgb.getThirdComponent();

            float y = (float) (16 + (65.738 * R) / 256 + (129.057 * G) / 256 + (25.064 * B) / 256);
            float cb = (float) (128 + (-37.945 * R) / 256 + (74.494 * G) / 256 + (112.439 * B) / 256);
            float cr = (float) (128 + (112.439 * R) / 256 + (94.154 * G) / 256 - (18.285 * B) / 256);

            ycbcrVector.add(new YCbCr(y, cb, cr));
        }

        return ycbcrVector;
    }

    /**
     * Take an RGB Vector and create another with the equivalent HSL values.
     * 
     * @param rgbVector
     * @return
     */
    public static Vector<ColorspaceInterface> ConvertToHSL( Vector<ColorspaceInterface> rgbVector )
    {
        Vector<ColorspaceInterface> hslVector = new Vector<ColorspaceInterface>();
        for ( ColorspaceInterface rgb : rgbVector )
        {

            float R = (float) ((rgb.getFirstComponent()) / 255.0);
            float G = (float) ((rgb.getSecondComponent()) / 255.0);
            float B = (float) ((rgb.getThirdComponent()) / 255.0);

            float min = R;
            if ( min > G ) min = G;
            if ( min > B ) min = B;

            float max = R;
            if ( max < G ) max = G;
            if ( max < B ) max = B;

            float delta = max - min;

            float l = (max + min) / 2;

            float tempS = -1;
            float tempH = -1;
            if ( delta == 0 )
            {
                tempS = 0;
                tempH = 0;
            } else
            {
                if ( l < 0.5 )
                    tempS = delta / (max + min);
                else
                    tempS = delta / (2 - max - min);

                float del_R = (((max - R) / 6) + (delta / 2)) / delta;
                float del_G = (((max - G) / 6) + (delta / 2)) / delta;
                float del_B = (((max - B) / 6) + (delta / 2)) / delta;

                if ( R == max )
                    tempH = del_B - del_G;
                else if ( G == max )
                    tempH = (1 / 3) + del_R - del_B;
                else if ( B == max ) tempH = (2 / 3) + del_G - del_R;

                if ( tempH < 0 ) tempH += 1;
                if ( tempH > 1 ) tempH -= 1;
            }

            float h = tempH;
            float s = tempS;

            hslVector.add(new HSL(h, s, l));
        }

        return hslVector;
    }

    /**
     * Increases the value of each color component by a certain delta.
     * @param colorspaceVector
     * @param delta
     */
    public static void IncreaseByDelta( Vector<ColorspaceInterface> colorspaceVector, float delta )
    {
        for ( ColorspaceInterface colorspace : colorspaceVector )
        {
            colorspace.setFirstComponent(colorspace.getFirstComponent() + delta);
            colorspace.setSecondComponent(colorspace.getSecondComponent() + delta);
            colorspace.setThirdComponent(colorspace.getThirdComponent() + delta);
        }
    }

    /**
     * Given an RGB Vector, decrease the resolution of the last color component from 8-bit to 3-bit
     * and print the image.
     * 
     * @param rgbVector
     */
    public static Vector<ColorspaceInterface> DecreaseBitResolution( Vector<ColorspaceInterface> rgbVector )
    {
        /*
         * My idea is decrease the resolution on the RGB Vector and then convert that vector to all
         * of the other colorspaces so we do a let less work.
         */
        Vector<ColorspaceInterface> rgbDecreased = new Vector<ColorspaceInterface>();

        for ( ColorspaceInterface colorspace : rgbVector )
        {
            int B = (int) colorspace.getThirdComponent();

            // Doing integer division to remove precision
            int key = B / 32;

            // Assigning new value with lower
            B = key * 32;

            // Assign it back to the vector
            rgbDecreased.add(new RGB((int) colorspace.getFirstComponent(), (int) colorspace.getSecondComponent(), B));
        }

        return rgbDecreased;
    }

    public static Vector<ColorspaceInterface> IncreaseBrightnessByPercentage( Vector<ColorspaceInterface> rgbVector,
            int percent )
    {

        for ( ColorspaceInterface rgb : rgbVector )
        {
            float R = rgb.getFirstComponent();

            float G = rgb.getSecondComponent();

            float B = rgb.getThirdComponent();

            float percentage = (float) percent / 100;

            float tempR = (R * percentage);

            R += tempR;

            R = (float) Math.ceil((double) R);

            if ( R > 255 )
            {
                R = 255;
            } else if ( R < 0 )
            {
                R = 0;
            }

            float tempG = (G * percentage);

            G += tempG;

            G = (float) Math.ceil((double) G);

            if ( G > 255 )
            {
                G = 255;
            } else if ( G < 0 )
            {
                G = 0;
            }

            float tempB = (B * percentage);

            B += tempB;

            B = (float) Math.ceil((double) B);

            if ( B > 225 )
            {
                B = 255;
            } else if ( B < 0 )
            {
                B = 0;
            }

            rgb.setFirstComponent(R);

            rgb.setSecondComponent(G);

            rgb.setThirdComponent(B);

        }

        return rgbVector;

    }

    public static Vector<ColorspaceInterface> IncreaseSaturationByPercentage( Vector<ColorspaceInterface> rgbVector,
            int percent )
    {

        for ( ColorspaceInterface rgb : rgbVector )
        {
            float R = rgb.getFirstComponent();

            float G = rgb.getSecondComponent();

            float B = rgb.getThirdComponent();

            int maxflag = 1;

            float percentage = (float) percent / 100;

            float tempR, tempG, tempB, decR, decG, decB;

            float max = R;
            if ( max < G )
            {
                max = G;
                maxflag = 2;
            }
            if ( max < B )
            {
                max = B;
                maxflag = 3;
            }

            if ( maxflag == 1 )
            {
                // increase R, decrease G and B
                tempR = R * percentage;
                R += tempR;
                R = (float) Math.ceil((double) R);

                if ( R > 255 )
                {
                    R = 255;
                } else if ( R < 0 )
                {
                    R = 0;
                }

                decR = tempR / 2;
                G -= decR;
                G = (float) Math.ceil((double) G);

                if ( G > 255 )
                {
                    G = 255;
                } else if ( G < 0 )
                {
                    G = 0;
                }

                B -= decR;

                B = (float) Math.ceil((double) B);

                if ( B > 255 )
                {
                    B = 255;
                } else if ( B < 0 )
                {
                    B = 0;
                }

                rgb.setFirstComponent(R);
                rgb.setSecondComponent(G);
                rgb.setThirdComponent(B);

            }

            else if ( maxflag == 2 )
            {
                // increase G, decrease R and B
                tempG = G * percentage;
                G += tempG;
                G = (float) Math.ceil((double) G);

                if ( G > 255 )
                {
                    G = 255;
                } else if ( G < 0 )
                {
                    G = 0;
                }

                decG = tempG / 2;
                R -= decG;

                R = (float) Math.ceil((double) R);

                if ( R > 255 )
                {
                    R = 255;
                } else if ( R < 0 )
                {
                    R = 0;
                }

                B -= decG;

                B = (float) Math.ceil((double) B);

                if ( B > 255 )
                {
                    B = 255;
                } else if ( B < 0 )
                {
                    B = 0;
                }

                rgb.setFirstComponent(R);
                rgb.setSecondComponent(G);
                rgb.setThirdComponent(B);

            }

            else if ( maxflag == 3 )
            {
                // increase B, decrease R and G
                tempB = B * percentage;
                B += tempB;

                B = (float) Math.ceil((double) B);

                if ( B > 255 )
                {
                    B = 255;
                } else if ( B < 0 )
                {
                    B = 0;
                }

                decB = tempB / 2;
                R -= decB;

                R = (float) Math.ceil((double) R);

                if ( R > 255 )
                {
                    R = 255;
                } else if ( R < 0 )
                {
                    R = 0;
                }

                G -= decB;

                G = (float) Math.ceil((double) G);

                if ( G > 255 )
                {
                    G = 255;
                } else if ( G < 0 )
                {
                    G = 0;
                }

                rgb.setFirstComponent(R);
                rgb.setSecondComponent(G);
                rgb.setThirdComponent(B);

            }
        }

        return rgbVector;

    }

    public static void NormalizeRGBValues( Vector<ColorspaceInterface> rgbVector )
    {
        for ( ColorspaceInterface rgb : rgbVector )
        {
            int R = (int)rgb.getFirstComponent();
            int G = (int)rgb.getSecondComponent();
            int B = (int)rgb.getThirdComponent();
            
            R = ( R > 255 ) ? 255 : ( R < 0 ) ? 0 : R ;
            G = ( G > 255 ) ? 255 : ( G < 0 ) ? 0 : G ;
            B = ( B > 255 ) ? 255 : ( B < 0 ) ? 0 : B ;
            
            rgb.setFirstComponent(R);
            rgb.setSecondComponent(G);
            rgb.setThirdComponent(B);
        }
    }
}
